﻿using System;
using System.Linq.Expressions;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.IO;
using Newtonsoft.Json;
using System.Reactive.Linq;
using Newtonsoft.Json.Serialization;

namespace QbservableCore.Serialization
{
    [DataContract]
    public class ConstantNode : ExpRepresentationBase
    {
        #region TypeInfo

        private TypeRepresentation _typeInfo;
        /// <summary>
        /// Gets or sets the type information.
        /// </summary>
        /// <value>
        /// The type information.
        /// </value>
        [DataMember]
        private TypeRepresentation TypeInfo
        {
            get { return _typeInfo; }
            set { _typeInfo = value; }
        }

        #endregion // TypeInfo

        #region Value

        /// <summary>
        /// Gets or sets the value.
        /// </summary>
        /// <value>
        /// The value.
        /// </value>
        [DataMember]
        public object Value { get; set; }

        #endregion // Value

        #region ToExpression

        /// <summary>
        /// hook conversion to expression.
        /// </summary>
        /// <returns></returns>
        protected internal override Expression ToExpression(ExepressionBuilderCache exepressionBuilderCache)
        {
            Type type = Value.GetType();
            var result = Expression.Constant(Value, type);
            return result;
        }

        #endregion // ToExpression

        protected override void TraverseChildren(Func<ExpRepresentationBase, ExpRepresentationBase> action) { }

        #region Hook on serialization and deserialization

        #region OnSerializing

        /// <summary>
        /// Called when [serializing].
        /// </summary>
        /// <param name="context">The context.</param>
        [OnSerializing]
        public void OnSerializing(StreamingContext context)
        {
            var stub = Value as IQbservableStub;
            if (stub == null)
                _typeInfo = (TypeRepresentation)Value.GetType();
            else
            {
                Value = string.Format(Consts.Json.StubFormat, stub.Key);
            }
        }

        #endregion // OnSerializing

        #region OnDeserialized

        /// <summary>
        /// Called when [deserialized].
        /// </summary>
        /// <param name="context">The context.</param>
        [OnDeserialized]
        public void OnDeserialized(StreamingContext context)
        {
            var convention = context.Context as IQbservableConvention;
            
            var valStr = Value as string;
            if (valStr != null && valStr.StartsWith(Consts.Json.StubPrefix))
            {
                var stubKey = valStr.Substring(Consts.Json.StubPrefix.Length);
                IQbservable qbs = convention.TargetResolver.ToQbservable(stubKey);
                if (qbs == null)
                {
                    throw new ArgumentNullException(
                        string.Format("Could not resolve stub : {0}", stubKey));
                }

                Value = qbs;
            }
            else
            {
                SerializationBinder binder = convention.JsonSettings.Binder;
                Type t = _typeInfo.InnerType;
                if (t != Value.GetType())
                {
                    Value = Convert.ChangeType(Value, t);
                }
            }
        }

        #endregion // OnDeserialized

        #endregion // Hook on serialization and deserialization

        #region ToString

        /// <summary>
        /// Returns a <see cref="System.String" /> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String" /> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            if (Value != null)
            {
                return Value.ToString();
            }
            return "null";
        }

        #endregion // ToString
    }
}